import 'dart:io';

import 'package:gate_cli/models/asset.dart';
import 'package:gate_cli/models/asset_config.dart';
import 'package:gate_cli/models/file_info.dart';
import 'package:gate_cli/utils/command_util.dart';
import 'package:gate_cli/utils/list_extension.dart';
import 'package:gate_cli/utils/print.dart';
import 'package:gate_cli/utils/string_extension.dart';
import 'package:code_builder/code_builder.dart';
import 'package:dart_style/dart_style.dart';
import 'package:path/path.dart';
import 'package:yaml/yaml.dart';

class AssetCommandUtil {
  AssetCommandUtil._internal();

  static final AssetCommandUtil _instance = AssetCommandUtil._internal();

  static AssetCommandUtil get instance => _instance;
  List<AssetInfo> assetList = [];

  void loadAssets(YamlMap pubspec, AssetConfig? assetConfig, String pkgName, bool skipCompress) {
    final assets = pubspec['flutter']?['assets'];
    if (assets == null) {
      print('pubspec.yaml 配置错误，未找到assets资源');
      return;
    }

    for (var asset in assets) {
      final dir = Directory('${Directory.current.path}/$asset');
      if (!dir.existsSync()) {
        printRed('ERROR! $asset 目录不存在，请检查配置');
        continue;
      }
      assetList += dir
          .listSync()

          /// 只读文件
          .where((element) => FileSystemEntity.isFileSync(element.path))

          /// 过滤隐藏文件
          .where((element) => !element.takeFileNameWithExe().startsWith('.'))
          .map((e) => convertFile(e, asset))
          .toList();
    }

    if (!skipCompress) {
      _compressImages(assetList);
    }

    final aMap = convertAssets(assetConfig, assets);
    genCode(aMap, assetConfig, pkgName);
  }

  /// 资源文件信息映射
  AssetInfo convertFile(FileSystemEntity fileSystemEntity, dynamic asset) {
    final fName = fileSystemEntity.takeFileNameWithExe();
    final key = fileSystemEntity.takeFileNameWithoutExe();
    final fPath = '$asset${fileSystemEntity.takeFilePath()}';
    final id = fileSystemEntity.hashCode;
    final assetPath = '$asset';
    return AssetInfo(fileName: fName, key: key, filePath: fPath, id: id, assetPath: assetPath);
  }

  /// 加载所有资源并归类
  Map<String, List<AssetMap>> convertAssets(AssetConfig? assetConfig, dynamic assets) {
    Map<String, List<AssetMap>> result = {};
    final source = assetConfig?.source;
    if (source != null) {
      for (var element in source) {
        final assets = sourceAsset(element);
        result[element.path ?? ''] = assets;
      }
    }
    for (String asset in assets) {
      final assets = sourceAsset(AssetSource(path: asset, dark: AssetSourceDark(enable: true)));
      final old = result[asset];
      if (old == null) {
        result[asset] = assets;
      } else {
        result[asset] = old + assets;
      }
    }

    /// 其余未匹配到的数据单独处理
    final maps = assetList
        .map((e) => AssetMap(key: e.key, light: e.filePath, parent: e.assetPath))
        .groupBy((element) => element.parent ?? '');
    maps.forEach((key, value) {
      final old = result[key];
      if (old == null) {
        result[key] = value;
      } else {
        result[key] = old + value;
      }
    });
    return result;
  }

  /// 资源转换
  List<AssetMap> sourceAsset(AssetSource assetSource) {
    if (assetSource.path.isNullOrEmpty) return List.empty();
    final source = assetList.where((element) => element.assetPath == assetSource.path);
    List<AssetMap> result = [];
    List<AssetInfo> deleteList = [];
    List<int> cacheIds = [];
    for (var value in source) {
      if (!assetList.any((element) => element.id == value.id)) continue;
      if (cacheIds.any((element) => value.id == element)) continue;
      cacheIds.add(value.id ?? 0);
      if (assetSource.dark?.enable == true) {
        final aInfo = takeDarkInfo(assetSource.dark?.path, value.key);
        if (aInfo != null && aInfo.id != value.id) {
          result.add(AssetMap(
              key: value.key,
              light: value.filePath,
              parent: value.assetPath,
              dark: aInfo.filePath,
              import: assetSource.dark?.import,
              condition: assetSource.dark?.condition));

          final del = assetList.firsOrNull((element) => element.id == aInfo.id);
          if (del != null) {
            deleteList.add(del);
          }
          deleteList.add(value);
        }
      } else {
        /// 不支持深色模式时
        result.add(AssetMap(key: value.key, light: value.filePath, parent: value.assetPath));
        cacheIds.add(value.id ?? 0);
        final del = assetList.firsOrNull((element) => element.id == value.id);
        if (del != null) {
          deleteList.add(del);
        }
      }
    }
    assetList.removeWhere((element) => deleteList.contains(element));
    return result;
  }

  /// 获取深色模式文件
  AssetInfo? takeDarkInfo(String? condition, String? key) {
    if (key.isNullOrEmpty) return null;
    final aInfo = assetList.where((element) => element.key == '${key}_dark');
    if (condition.isNullOrEmpty) {
      /// 默认配置
      return aInfo.firsOrNull((element) => element.key?.endsWith('_dark') == true);
    }

    /// 自定义配置
    return aInfo.firsOrNull((element) => element.assetPath == condition);
  }

  void genCode(Map<String, List<AssetMap>> aMap, AssetConfig? assetConfig, String pkgName) {
    genResource(aMap, assetConfig);
    aMap.forEach((key, values) {
      if (values.isNotEmpty) {
        final fileName = key.split('/').lastWhere((element) => element.isNotEmpty);
        genAsset(fileName, values, assetConfig, pkgName);
      }
    });
  }

  /// 构建资源
  void genResource(Map<String, List<AssetMap>> aMap, AssetConfig? assetConfig) {
    String part = '''
/// 构建时间:${DateTime.now()}

    ''';
    if (assetConfig?.source?.any((element) => element.dark?.enable ?? true) ?? true) {
      Set<String> sets = {};
      assetConfig?.source?.map((e) => e.dark?.import).forEach((element) {
        sets.add(element ?? '''import 'package:lib_uikit/theme/gt_theme_provider.dart';''');
      });

      /// 未指定加载配置时，默认加入深色配置包的引用
      if (sets.isEmpty) {
        sets.add('''import 'package:lib_uikit/theme/gt_theme_provider.dart';''');
      }
      for (var element in sets) {
        part += '''
$element
      ''';
      }
    }
    aMap.forEach((key, value) {
      if (value.isNotEmpty) {
        final fileName = key.split('/').lastWhere((element) => element.isNotEmpty);
        part += '''
        
part '$fileName.dart';
        ''';
      }
    });
    final formatter = DartFormatter(fixes: StyleFix.all);
    writeFile(assetConfig?.name ?? 'resource', formatter.format(part), assetConfig);
  }

  /// 写入数据
  /// 若[append]为true时则需要追加文件
  void writeFile(String fileName, String content, AssetConfig? assetConfig, {bool append = false}) {
    final dartFile = dartFileName(fileName, assetConfig);
    final file = File(dartFile);
    if (!file.existsSync()) {
      file.createSync(recursive: true);
    }
    file.writeAsStringSync(content, mode: append ? FileMode.append : FileMode.write);
  }

  /// dart 文件处理
  String dartFileName(String fileName, AssetConfig? assetConfig) {
    final package = assetConfig?.package ?? 'lib/resource';
    final directory = Directory(package);
    if (!directory.existsSync()) {
      directory.createSync(recursive: true);
    }
    return package.endsWith('/') ? '$package$fileName.dart' : '$package/$fileName.dart';
  }

  /// 构建资源
  void genAsset(String fileName, List<AssetMap> values, AssetConfig? assetConfig, String pkgName) {
    final emitter = DartEmitter();
    final jClz = Class((cb) => cb
      ..docs.add('/// 构建时间:${DateTime.now()}')
      ..docs.add('part of "${assetConfig?.name ?? 'resources'}.dart";')
      ..name = fileName.formatClassName()
      ..methods.add(Method((m) => m.name = '${fileName.formatClassName()}._'))
      ..methods.addAll(genMethod(values, pkgName)));
    final code = jClz.accept(emitter);
    final formatter = DartFormatter(fixes: StyleFix.all);
    writeFile(fileName, formatter.format('$code'), assetConfig);
  }

  /// 构建方法
  List<Method> genMethod(List<AssetMap> values, String pkgName) {
    List<Method> methods = [];
    values.forEach((element) {
      var name = '${element.key?.formatAssetName()}';
      // 首字母为数字时拼接 路径
      if (name[0].isDigit()) {
        var parent = element.parent ?? "unknown";
        if (parent.endsWith("/")) {
          parent = parent.substring(0, parent.length - 1);
        }
        final paths = parent.split("/");
        name = paths.last + name;
      }
      methods.add(Method((m) => m
        ..name = '$name'
        ..type = MethodType.getter
        ..docs.add('/// ${element.key}')
        ..static = true
        ..body = Code(genGetter(element, pkgName))));
    });
    return methods;
  }

  /// 方法体内容
  String genGetter(AssetMap map, String pkgName) {
    if (map.dark.isNullOrEmpty) {
      return '''return "packages/$pkgName/${map.light}";''';
    }
    return '''
    if(${map.condition ?? 'GTThemeProvider.instance.isDark()'}) {
      return "packages/$pkgName/${map.dark}";
    }
    return "packages/$pkgName/${map.light}";
    ''';
  }
}

/// 压缩图片
/// 如果存在需要压缩的图片文件，压缩文件并修改podspec后，重新执行当前命令
void _compressImages(List<AssetInfo> assetList) {
  bool shouldRerun = false;
  String pubspecText = File('pubspec.yaml').readAsStringSync();

  for (var element in assetList) {
    final filePath = element.filePath;
    if (filePath == null) {
      continue;
    }
    final ext = extension(filePath).toLowerCase();
    if (!{'.png', '.jpg', '.jpeg'}.contains(ext)) {
      continue;
    }

    /// 存在需要压缩的文件，下载cwebp
    final cwebpPath = _downloadCwebp();
    if (cwebpPath == null) {
      return;
    }

    /// 压缩图片文件
    final inputFilePath = '${Directory.current.path}/$filePath';
    final outputFilePath = inputFilePath.replaceFirst(ext, '.webp');
    final result = Process.runSync(cwebpPath, [inputFilePath, '-o', outputFilePath]);
    if (result.exitCode == 0) {
      print('压缩图片: $filePath ${File(inputFilePath).lengthSync()} -> ${File(outputFilePath).lengthSync()}');
      shouldRerun = true;

      // 删除原文件
      Process.runSync('rm', [inputFilePath]);
      // 修改 pubspec.yaml 中可能存在的路径引用
      final relativePath = outputFilePath.replaceFirst('${Directory.current.path}/', '');
      pubspecText = pubspecText.replaceFirst(filePath, relativePath);
    } else {
      printRed('压缩图片失败: $filePath - ${result.stderr}');
    }
  }

  if (shouldRerun) {
    // 重新写入 pubspec.yaml
    File('pubspec.yaml').writeAsStringSync(pubspecText);

    // 重新执行
    printYellow('重新执行: dart run gate_cli ${CommandUtil.arguments.join(' ')} --skip-compress=true');
    Process.runSync(
      'dart',
      ['run', 'gate_cli'] + CommandUtil.arguments + ['--skip-compress=true'],
      runInShell: true,
    );
    exit(0);
  }
}

/// 下载cwebp
String? _downloadCwebp() {
  const libwebpVersion = 'libwebp-1.3.2-mac-arm64';

  var parts = split(Directory.current.path);
  if (parts.length < 3) {
    return null;
  }

  final gateCliDirectoryPath = joinAll(parts.sublist(0, 3) + ['.ci_gate_cli']);
  final cwebpPath = join(gateCliDirectoryPath, 'cwebp');
  if (File(cwebpPath).existsSync()) {
    return cwebpPath;
  }
  printYellow('cwebp 未安装');

  if (!Directory(gateCliDirectoryPath).existsSync()) {
    Directory(gateCliDirectoryPath).createSync();
  }

  final outputPath = join(gateCliDirectoryPath, '$libwebpVersion.tar.gz');
  if (!File(outputPath).existsSync()) {
    final downloadUrl = 'https://storage.googleapis.com/downloads.webmproject.org/releases/webp/$libwebpVersion.tar.gz';
    print('开始下载 libwebp...');
    final result = Process.runSync('curl', ['-o', outputPath, downloadUrl]);
    if (result.exitCode != 0) {
      printRed('libwebp 下载失败 ${result.stderr}');
      return null;
    }
  }

  final tar = Process.runSync('tar', ['-xvf', outputPath, '-C', gateCliDirectoryPath]);
  if (tar.exitCode != 0) {
    printRed('libwebp 解压失败 ${tar.stderr}');
    Process.runSync('rm', [outputPath]);
    return null;
  }
  Process.runSync('rm', [outputPath]);

  final downloadCwebpPath = join(gateCliDirectoryPath, libwebpVersion, 'bin', 'cwebp');
  if (!File(downloadCwebpPath).existsSync()) {
    printRed('cwebp 未找到');
    return null;
  }

  Process.runSync('mv', [downloadCwebpPath, '${cwebpPath}']);
  Process.runSync('rm', ['-r', '${gateCliDirectoryPath}/$libwebpVersion']);
  print('cwebp 已安装: ${cwebpPath}');
  return cwebpPath;
}
